package com.chenwc;

import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.activation.MimetypesFileTypeMap;
import java.io.*;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author chenwc
 * @package com.chenwc
 * @project ObjectStorage
 * @date 2024/4/23
 */
public class ObsUtil {
    private static final Logger logger = LoggerFactory.getLogger(ObsUtil.class);

    /**
     * obsClient
     */
    private static ObsClient obsClient = null;

    /**
     * ak
     */
    private static String accessKey;
    /**
     * sk
     */
    private static String secretKey;
    /**
     * endpoint
     */
    private static String endPoint;

    /**
     * 文件分块大小,10M
     */
    private static final Long CHUNK_FILE_SIZE = (long) (10 * 1024 * 1024);

    /**
     * 文件分块阈值,10M
     */
    private static final Integer CHUNK_FILE_SIZE_THRESHOLD = 10 * 1024 * 1024;

    /**
     * 构造函数
     *
     * @param userAccessKey ak
     * @param userSecretKey sk
     * @param userEndpoint  endpoint
     */
    public static void setObsClient(String userAccessKey, String userSecretKey, String userEndpoint) {
        accessKey = userAccessKey;
        secretKey = userSecretKey;
        endPoint = userEndpoint;
    }

    /**
     * 初始化obsClient
     */
    public static void setObsClient() {
        if (obsClient == null) {
            // 创建ObsClient实例
            // 使用永久AK/SK初始化客户端
            obsClient = new ObsClient(accessKey, secretKey, endPoint);
            // 使用临时AK/SK和SecurityToken初始化客户端
            // obsClient = new ObsClient(ak, sk, securityToken, endPoint);

            // 创建ObsConfiguration配置类实例
            //ObsConfiguration config = new ObsConfiguration();
            //config.setEndPoint(endPoint);
            //自定义域名
            //config.setCname(true);
            // 创建ObsClient实例
            //obsClient = new ObsClient(ak, sk, config);
        }
    }

    public static ObsClient getObsClient() {
        if (obsClient == null) {
            setObsClient();
        }
        return obsClient;
    }

    /**
     * 关闭obsClient
     */
    public static void close() {
        if (obsClient != null) {
            try {
                obsClient.close();
            } catch (IOException e) {
                logger.error("obsClient close error", e);
            }
        }
    }

    /**
     * 创建桶
     *
     * @param bucketName 桶名称
     * @param location   桶区域位置
     */
    public static void createBucket(String bucketName, String location) {
        setObsClient();
        try {
            CreateBucketRequest request = new CreateBucketRequest();
            request.setBucketName(bucketName);
            // 设置桶访问权限为私有读写，默认也是私有读写
            request.setAcl(AccessControlList.REST_CANNED_PRIVATE);
            // 设置桶的存储类别为标准存储
            request.setBucketStorageClass(StorageClassEnum.STANDARD);
            // 设置桶区域位置，location 需要与 endpoint的位置信息一致
            request.setLocation(location);
            // 指定创建多AZ桶，如果不设置，默认创建单AZ桶
            request.setAvailableZone(AvailableZoneEnum.MULTI_AZ);
            // 创建桶
            ObsBucket bucket = obsClient.createBucket(request);
            logger.info("createBucket成功，bucketName：{}", bucket.getBucketName());
        } catch (Exception e) {
            logger.error("createBucket error", e);
        }
    }

    /**
     * 列举桶
     */
    public static List<ObsBucket> listBucket() {
        setObsClient();
        try {
            // 列举桶
            ListBucketsRequest request = new ListBucketsRequest();
            request.setQueryLocation(true);
            return obsClient.listBuckets(request);
        } catch (Exception e) {
            logger.error("listBucket error", e);
        }
        return new ArrayList<>();
    }

    /**
     * 删除桶
     *
     * @param bucketName 桶名称
     */
    public static void deleteBucket(String bucketName) {
        setObsClient();
        try {
            // 删除桶
            HeaderResponse response = obsClient.deleteBucket(bucketName);
            logger.info("deleteBucket成功，requestId: {}", response.getRequestId());
            logger.info("deleteBucket成功，statusCode: {}", response.getStatusCode());
        } catch (Exception e) {
            logger.error("deleteBucket error", e);
        }
    }

    /**
     * 判断桶是否存在
     *
     * @param bucketName 桶名称
     */
    public static boolean isExistBucket(String bucketName) {
        setObsClient();
        try {
            // 获取桶信息
            return obsClient.headBucket(bucketName);
        } catch (Exception e) {
            logger.error("headBucket error", e);
        }
        return false;
    }

    /**
     * 获取桶元数据
     *
     * @param bucketName 桶名称
     */
    public static BucketMetadataInfoResult getBucketMetadata(String bucketName) {
        setObsClient();
        try {
            //示例Origin
            //String exampleOrigin = "http://www.exampleorigin.com";
            BucketMetadataInfoRequest request = new BucketMetadataInfoRequest(bucketName);
            //只有在涉及 cors 时才需要 setOrigin
            //request.setOrigin(exampleOrigin);
            // 获取桶元数据
            return obsClient.getBucketMetadata(request);
        } catch (Exception e) {
            logger.error("getBucketMetadata error", e);
        }
        return null;
    }

    /**
     * 设置桶访问权限
     *
     * @param bucketName 桶名称
     * @param acl        访问权限
     */
    public static void setBucketAcl(String bucketName, AccessControlList acl) {
        setObsClient();
        try {
            // 为桶设置预定义访问策略
            obsClient.setBucketAcl(bucketName, acl);

            //设置桶的访问策略
            //示例桶名
//            String exampleBucket = "examplebucket";
//            //示例userid
//            String exampleUserid = "userid";
//            //示例userid
//            String exampleOwnerId = "ownerid";
//            AccessControlList acl = new AccessControlList();
//            Owner owner = new Owner();
//            owner.setId(exampleOwnerId);
//            acl.setOwner(owner);
//            // 为指定用户设置完全控制权限
//            acl.grantPermission(new CanonicalGrantee(exampleUserid), Permission.PERMISSION_FULL_CONTROL);
//            // 为所有用户设置读权限
//            acl.grantPermission(GroupGrantee.ALL_USERS, Permission.PERMISSION_READ);
//            // 直接设置桶访问权限
//            obsClient.setBucketAcl(exampleBucket, acl);
        } catch (Exception e) {
            logger.error("setBucketAcl error", e);
        }
    }

    /**
     * 获取桶访问权限
     *
     * @param bucketName 桶名称
     */
    public static AccessControlList getBucketAcl(String bucketName) {
        setObsClient();
        try {
            // 获取桶访问权限
            return obsClient.getBucketAcl(bucketName);
        } catch (Exception e) {
            logger.error("getBucketAcl error", e);
        }
        return null;
    }

    /**
     * 设置桶策略
     *
     * @param bucketName 桶名称
     * @param policy     策略
     */
    public static void setBucketPolicy(String bucketName, String policy) {
        setObsClient();
        try {
            // 设置桶策略
            obsClient.setBucketPolicy(bucketName, policy);
        } catch (Exception e) {
            logger.error("setBucketPolicy error", e);
        }
    }

    /**
     * 获取桶策略
     *
     * @param bucketName 桶名称
     */
    public static String getBucketPolicy(String bucketName) {
        setObsClient();
        try {
            // 获取桶策略
            return obsClient.getBucketPolicy(bucketName);
        } catch (Exception e) {
            logger.error("getBucketPolicy error", e);
        }
        return null;
    }

    /**
     * 删除桶策略
     *
     * @param bucketName 桶名称
     */
    public static void deleteBucketPolicy(String bucketName) {
        setObsClient();
        try {
            // 删除桶策略
            obsClient.deleteBucketPolicy(bucketName);
        } catch (Exception e) {
            logger.error("deleteBucketPolicy error", e);
        }
    }

    /**
     * 获取桶区域位置
     *
     * @param bucketName 桶名称
     */
    public static String getBucketLocation(String bucketName) {
        setObsClient();
        try {
            // 获取桶策略
            return obsClient.getBucketLocation(bucketName);
        } catch (Exception e) {
            logger.error("getBucketLocation error", e);
        }
        return null;
    }

    /**
     * 获取桶存储存量信息
     *
     * @param bucketName 桶名称
     */
    public static BucketStorageInfo getBucketStorageInfo(String bucketName) {
        setObsClient();
        try {
            // 获取桶策略
            return obsClient.getBucketStorageInfo(bucketName);
        } catch (Exception e) {
            logger.error("getBucketStorageInfo error", e);
        }
        return null;
    }

    /**
     * 获取桶配额
     *
     * @param bucketName 桶名称
     */
    public static BucketQuota getBucketQuota(String bucketName) {
        setObsClient();
        try {
            // 获取桶策略
            return obsClient.getBucketQuota(bucketName);
        } catch (Exception e) {
            logger.error("getBucketQuota error", e);
        }
        return null;
    }

    /**
     * 设置桶配额
     *
     * @param bucketName  桶名称
     * @param bucketQuota 桶配额
     */
    public static void setBucketQuota(String bucketName, BucketQuota bucketQuota) {
        setObsClient();
        try {
            // 获取桶策略
            obsClient.setBucketQuota(bucketName, bucketQuota);
        } catch (Exception e) {
            logger.error("setBucketQuota error", e);
        }
    }

    /**
     * 获取桶存储策略
     *
     * @param bucketName 桶名称
     * @return BucketStoragePolicyConfiguration
     */
    public static BucketStoragePolicyConfiguration getBucketStoragePolicy(String bucketName) {
        setObsClient();
        try {
            return obsClient.getBucketStoragePolicy(bucketName);
        } catch (Exception e) {
            logger.error("setBucketStoragePolicy error", e);
        }
        return null;
    }

    /**
     * 流式上传
     *
     * @param bucketName  桶名称
     * @param objectKey   对象名称
     * @param inputStream 输入流
     */
    public static void putObjectByStream(String bucketName, String objectKey, InputStream inputStream) {
        setObsClient();
        try {
            PutObjectRequest request = new PutObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(objectKey);
            request.setInput(inputStream);
            obsClient.putObject(request);

            // 上传字符串（byte数组）
            //String content = "Hello OBS";
            //obsClient.putObject("examplebucket", "objectname", new ByteArrayInputStream(content.getBytes()));
        } catch (Exception e) {
            logger.error("putObjectByStream error", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                logger.error("putObjectByStream error", e);
            }
        }
    }

    /**
     * 文件上传
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @param file       文件
     */
    public static void putObjectByFile(String bucketName, String objectKey, File file) {
        setObsClient();
        try {
            PutObjectRequest request = new PutObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(objectKey);
            request.setFile(file);
            obsClient.putObject(request);
        } catch (Exception e) {
            logger.error("putObjectByFile error", e);
        }
    }

    /**
     * 文件上传（带上传进度）
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @param file       文件
     */
    public static void putObjectByFileByProgress(String bucketName, String objectKey, File file) {
        setObsClient();
        try {
            // 文件上传
            PutObjectRequest request = new PutObjectRequest(bucketName, objectKey);
            request.setFile(file);
            request.setProgressListener(
                    status -> {
                        // 获取上传平均速率
                        logger.info("平均速度:" + status.getAverageSpeed());
                        // 获取上传进度百分比
                        logger.info("上传百分比:" + status.getTransferPercentage());
                    });
            // 每上传1MB数据反馈上传进度
            request.setProgressInterval(1024 * 1024L);
            obsClient.putObject(request);
        } catch (Exception e) {
            logger.error("putObjectByFileByProgress error", e);
        }
    }

    /**
     * 创建文件夹
     *
     * @param bucketName 桶名称
     * @param folderName 文件夹名称
     */
    public static void createFolder(String bucketName, String folderName) {
        setObsClient();
        try {
            if (!folderName.endsWith("/")) {
                folderName = folderName + "/";
            }
            // 创建文件夹
            obsClient.putObject(bucketName, folderName, new ByteArrayInputStream(new byte[0]));
        } catch (Exception e) {
            logger.error("createFolder error", e);
        }
    }

    /**
     * 文件上传（带元数据）
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @param file       文件
     * @param metadata   元数据
     */
    public static void putObjectByObjectMetadata(String bucketName, String objectKey, File file, ObjectMetadata metadata) {
        setObsClient();
        try {
            obsClient.putObject(bucketName, objectKey, file, metadata);
        } catch (Exception e) {
            logger.error("putObjectByObjectMetadata error", e);
        }
    }

    /**
     * 文件上传（分段上传）
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @param largeFile  文件
     */
    public static void multipartUploadObject(String bucketName, String objectKey, File largeFile) {
        setObsClient();
        try {
            // 初始化线程池
            ExecutorService executorService = Executors.newFixedThreadPool(20);
            // 初始化分段上传任务
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey);
            InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
            String uploadId = result.getUploadId();
            logger.info("uploadId: " + uploadId);
            long fileSize = largeFile.length();
            if (fileSize <= CHUNK_FILE_SIZE_THRESHOLD) {
                obsClient.putObject(bucketName, objectKey, largeFile);
                logger.info("文件小于: {}，直接上传", CHUNK_FILE_SIZE_THRESHOLD);
                return;
            }
            // 计算需要上传的段数
            long partCount = fileSize % CHUNK_FILE_SIZE == 0 ? fileSize / CHUNK_FILE_SIZE : fileSize / CHUNK_FILE_SIZE + 1;
            List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<>());
            // 执行并发上传段
            for (int i = 0; i < partCount; i++) {
                // 分段在文件中的起始位置
                long offset = i * CHUNK_FILE_SIZE;
                // 分段大小
                long currPartSize = (i + 1 == partCount) ? fileSize - offset : CHUNK_FILE_SIZE;
                // 分段号
                final int partNumber = i + 1;
                executorService.execute(
                        () -> {
                            UploadPartRequest uploadPartRequest = new UploadPartRequest();
                            uploadPartRequest.setBucketName(bucketName);
                            uploadPartRequest.setObjectKey(objectKey);
                            uploadPartRequest.setUploadId(uploadId);
                            uploadPartRequest.setFile(largeFile);
                            uploadPartRequest.setPartSize(currPartSize);
                            uploadPartRequest.setOffset(offset);
                            uploadPartRequest.setPartNumber(partNumber);
                            UploadPartResult uploadPartResult;
                            try {
                                uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                                logger.info("Part#" + partNumber + " done");
                                partEtags.add(
                                        new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
                            } catch (ObsException e) {
                                logger.error("multipartUploadObject error", e);
                            }
                        });
            }
            // 等待上传完成
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    logger.error("multipartUploadObject error", e);
                }
            }
            // 合并段
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partEtags);
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
            logger.info("completeMultipartUpload successfully");
        } catch (Exception e) {
            logger.error("multipartUploadObject error", e);
        }
    }

    /**
     * 取消分段上传任务
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param uploadId   分段上传任务ID
     */
    public static void abortMultipartUpload(String bucketName, String key, String uploadId) {
        setObsClient();
        try {
            // 取消分段上传任务请求
            AbortMultipartUploadRequest request = new AbortMultipartUploadRequest(bucketName, key, uploadId);
            // 取消分段上传任务接口
            obsClient.abortMultipartUpload(request);
        } catch (Exception e) {
            logger.error("abortMultipartUpload error", e);
        }
    }

    /**
     * 列举所有已上传的段
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param uploadId   分段上传任务ID
     * @return 段列表
     */
    public static List<Multipart> listMultipartUploadParts(String bucketName, String key, String uploadId) {
        setObsClient();
        List<Multipart> parts = new ArrayList<>();
        try {
            // 列举所有已上传的段，其中uploadId来自于initiateMultipartUpload
            ListPartsRequest request = new ListPartsRequest("examplebucket", "objectname");
            request.setUploadId(uploadId);
            ListPartsResult result;
            do {
                result = obsClient.listParts(request);
                parts.addAll(result.getMultipartList());
                request.setPartNumberMarker(Integer.parseInt(result.getNextPartNumberMarker()));
            } while (result.isTruncated());
            return parts;
        } catch (Exception e) {
            logger.error("ListMultipartUploadParts error", e);
        }
        return parts;
    }

    /**
     * 列举所有分段上传任务
     *
     * @param bucketName 桶名称
     * @param targetKey  对象名称
     * @return 分段上传任务列表
     */
    public static List<MultipartUpload> listMultipartUploadTask(String bucketName, String targetKey) {
        setObsClient();
        List<MultipartUpload> uploads = new ArrayList<>();
        try {
            ListMultipartUploadsRequest request = new ListMultipartUploadsRequest(bucketName);
            MultipartUploadListing result;
            do {
                result = obsClient.listMultipartUploads(request);
                for (MultipartUpload upload : result.getMultipartTaskList()) {
                    if (targetKey.equals(upload.getObjectKey())) {
                        uploads.add(upload);
                    }
                }
                request.setKeyMarker(result.getNextKeyMarker());
                request.setUploadIdMarker(result.getNextUploadIdMarker());
            } while (result.isTruncated());
        } catch (Exception e) {
            logger.error("listMultipartUploadTask error", e);
        }
        return uploads;
    }

    /**
     * 追加上传
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param uploadData 待上传数据
     * @param position   追加位置
     * @return 下次追加位置，失败返回-1
     */
    public static long appendObject(String bucketName, String key, byte[] uploadData, long position) {
        setObsClient();
        try {
            // 第一次追加上传
            AppendObjectRequest request = new AppendObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(key);
            request.setPosition(position);
            request.setInput(new ByteArrayInputStream(uploadData));
            AppendObjectResult result = obsClient.appendObject(request);
            return result.getNextPosition();
        } catch (Exception e) {
            logger.error("appendObject error", e);
        }
        return -1;
    }

    /**
     * 获取对象下一次追加位置
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param versionId  对象版本，为空时获取最新版本
     * @return 下次追加位置，失败返回-1
     */
    public static long getObjectNextPosition(String bucketName, String key, String versionId) {
        setObsClient();
        try {
            // 获取对象信息
            ObjectMetadata metadata;
            if (StringUtils.isNotBlank(versionId)) {
                metadata = obsClient.getObjectMetadata(bucketName, key, versionId);
            } else {
                metadata = obsClient.getObjectMetadata(bucketName, key);
            }
            return metadata.getNextPosition();
        } catch (Exception e) {
            logger.error("getObjectPosition error", e);
        }
        return -1;
    }

    /**
     * 断点续传上传
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param localFile  本地文件
     */
    public static void breakpointContinuationUpload(String bucketName, String key, File localFile) {
        setObsClient();
        try {
            UploadFileRequest request = new UploadFileRequest(bucketName, key);
            // 设置待上传的本地文件，localfile为待上传的本地文件路径，需要指定到具体带文件后缀的文件名
            request.setUploadFile(localFile.getAbsolutePath());
            // 设置分段上传时的最大并发数
            request.setTaskNum(5);
            // 设置分段大小为10MB
            request.setPartSize(CHUNK_FILE_SIZE);
            // 开启断点续传模式
            request.setEnableCheckpoint(true);
            // 进行断点续传上传
            CompleteMultipartUploadResult result = obsClient.uploadFile(request);
            logger.info("breakpointContinuationUpload success, requestId:{}", result.getRequestId());
            logger.info("breakpointContinuationUpload success, statusCode:{}", result.getStatusCode());
        } catch (Exception e) {
            logger.error("breakpointContinuationUpload error", e);
        }
    }

    /**
     * 获取PostObject表单上传所需参数
     *
     * @param localFile 本地文件
     * @return 表单参数
     */
    public static Map<String, Object> getPostObjectFormData(File localFile) {
        setObsClient();
        // 设置表单参数
        Map<String, Object> formParams = new HashMap<>();
        try {
            // 生成基于表单上传的请求
            PostSignatureRequest request = new PostSignatureRequest();
            // 设置对象访问权限为公共读
            formParams.put("x-obs-acl", "public-read");
            String contentType = new MimetypesFileTypeMap().getContentType(localFile);
            if (contentType == null || contentType.isEmpty()) {
                contentType = "application/octet-stream";
            }
            // 设置对象MIME类型
            formParams.put("content-type", contentType);
            request.setFormParams(formParams);
            // 设置表单上传请求有效期，单位：秒
            request.setExpires(3600);
            PostSignatureResponse response = obsClient.createPostSignature(request);
            formParams.put("policy", response.getPolicy());
            formParams.put("signature", response.getSignature());
            formParams.put("token", response.getToken());
        } catch (Exception e) {
            logger.error("getPostObjectFormData error", e);
        }
        return formParams;
    }

    /**
     * 流式下载
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param versionId      对象版本，为空时获取最新版本
     * @param outputFilePath 输出文件路径
     */
    public static void downloadObjectByStream(String bucketName, String key, String versionId, String outputFilePath) {
        setObsClient();
        ByteArrayOutputStream bos = null;
        InputStream input = null;
        try {
            // 流式下载
            ObsObject obsObject;
            if (StringUtils.isNotBlank(versionId)) {
                obsObject = obsClient.getObject(bucketName, key, versionId);
            } else {
                obsObject = obsClient.getObject(bucketName, key);
            }
            input = obsObject.getObjectContent();
            byte[] b = new byte[1024];
            bos = new ByteArrayOutputStream();
            int len;
            while ((len = input.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            FileUtils.writeByteArrayToFile(new File(outputFilePath), bos.toByteArray());
        } catch (Exception e) {
            logger.error("downloadObject error", e);
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (input != null) {
                    input.close();
                }
            } catch (Exception e) {
                logger.error("close error", e);
            }
        }
    }

    /**
     * 流式下载（带进度条）
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param versionId      对象版本，为空时获取最新版本
     * @param outputFilePath 输出文件路径
     */
    public static void downloadObjectByProgress(String bucketName, String key, String versionId, String outputFilePath) {
        setObsClient();
        ByteArrayOutputStream bos = null;
        InputStream input = null;
        try {
            // 获取下载进度
            GetObjectRequest request;
            if (StringUtils.isNotBlank(versionId)) {
                request = new GetObjectRequest(bucketName, key, versionId);
            } else {
                request = new GetObjectRequest(bucketName, key);
            }
            request.setProgressListener(
                    status -> {
                        // 获取下载平均速率
                        logger.info("平均速度:" + status.getAverageSpeed());
                        // 获取下载进度百分比
                        logger.info("下载百分比:" + status.getTransferPercentage());
                    });
            // 每下载1MB数据反馈下载进度
            request.setProgressInterval(1024 * 1024L);
            ObsObject obsObject = obsClient.getObject(request);
            // 读取对象内容
            input = obsObject.getObjectContent();
            byte[] b = new byte[1024];
            bos = new ByteArrayOutputStream();
            int len;
            while ((len = input.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            FileUtils.writeByteArrayToFile(new File(outputFilePath), bos.toByteArray());
        } catch (Exception e) {
            logger.error("downloadObject error", e);
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (input != null) {
                    input.close();
                }
            } catch (Exception e) {
                logger.error("close error", e);
            }
        }
    }

    /**
     * 恢复归档对象
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param versionId      对象版本，为空时获取最新版本
     * @param outputFilePath 输出文件路径
     */
    public static void restoreObject(String bucketName, String key, String versionId, String outputFilePath) {
        getObsClient();
        ByteArrayOutputStream bos = null;
        InputStream input = null;
        try {
            // 下载归档存储对象
            RestoreObjectRequest request = new RestoreObjectRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(key);
            request.setDays(1);
            request.setRestoreTier(RestoreTierEnum.EXPEDITED);
            obsClient.restoreObject(request);
            // 等待对象恢复
            Thread.sleep(60 * 6 * 1000);
            // 下载对象
            ObsObject obsObject;
            if (StringUtils.isNotBlank(versionId)) {
                obsObject = obsClient.getObject(bucketName, key, versionId);
            } else {
                obsObject = obsClient.getObject(bucketName, key);
            }
            // 读取对象内容
            input = obsObject.getObjectContent();
            byte[] b = new byte[1024];
            bos = new ByteArrayOutputStream();
            int len;
            while ((len = input.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            FileUtils.writeByteArrayToFile(new File(outputFilePath), bos.toByteArray());
        } catch (Exception e) {
            logger.error("restoreObject error", e);
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (input != null) {
                    input.close();
                }
            } catch (Exception e) {
                logger.error("close error", e);
            }
        }
    }

    /**
     * 断点续传下载
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param versionId      对象版本，为空时获取最新版本
     * @param outputFilePath 输出文件路径
     */
    public static void breakpointContinuationDownload(String bucketName, String key, String versionId, String outputFilePath) {
        getObsClient();
        try {
            DownloadFileRequest request;
            if (StringUtils.isNotBlank(versionId)) {
                request = new DownloadFileRequest(bucketName, key, versionId);
            } else {
                request = new DownloadFileRequest(bucketName, key);
            }
            // 设置下载对象的本地文件路径
            request.setDownloadFile(outputFilePath);
            // 设置分段下载时的最大并发数
            request.setTaskNum(5);
            // 设置分段大小为10MB
            request.setPartSize(CHUNK_FILE_SIZE);
            // 开启断点续传模式
            request.setEnableCheckpoint(true);
            // 进行断点续传下载
            DownloadFileResult result = obsClient.downloadFile(request);
            logger.info("Etag: " + result.getObjectMetadata().getEtag());
        } catch (Exception e) {
            logger.error("breakpointContinuationDownload error", e);
        }
    }

    /**
     * 下载对象接口实现图片处理
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param outputFilePath 输出文件路径
     * @param imageProcess   图片处理参数，如果不输入处理命令，将返回原图。
     */
    public static void downloadObjectProgressImages(String bucketName, String key, String outputFilePath, String imageProcess) {
        getObsClient();
        ByteArrayOutputStream bos = null;
        InputStream input = null;
        try {
            // 发起图片处理请求并下载
            GetObjectRequest request = new GetObjectRequest(bucketName, key);
            // 设置图片处理参数，对图片依次进行缩放、旋转
            request.setImageProcess(imageProcess);
            ObsObject obsObject = obsClient.getObject(request);
            // 读取对象内容
            input = obsObject.getObjectContent();
            byte[] b = new byte[1024];
            bos = new ByteArrayOutputStream();
            int len;
            while ((len = input.read(b)) != -1) {
                bos.write(b, 0, len);
            }
            FileUtils.writeByteArrayToFile(new File(outputFilePath), bos.toByteArray());
        } catch (Exception e) {
            logger.error("downloadObjectProgressImages error", e);
        } finally {
            try {
                if (bos != null) {
                    bos.close();
                }
                if (input != null) {
                    input.close();
                }
            } catch (Exception e) {
                logger.error("close error", e);
            }
        }
    }

    /**
     * 设置对象元数据
     *
     * @param bucketName     桶名称
     * @param key            对象名称
     * @param objectMetadata 对象元数据
     * @param userHeaders    用户自定义头域
     */
    public static void setObjectMetadata(String bucketName, String key, ObjectMetadata objectMetadata, HashMap<String, String> userHeaders) {
        getObsClient();
        try {
            // 设置对象元数据
            SetObjectMetadataRequest request = new SetObjectMetadataRequest(bucketName, key);
            // 对象的 HTTP 标准头域
            request.setContentType(objectMetadata.getContentType());
            request.setExpires(objectMetadata.getExpires());
            request.setContentDisposition(objectMetadata.getContentDisposition());
            request.setContentEncoding(objectMetadata.getContentEncoding());
            request.setContentLanguage(objectMetadata.getContentLanguage());
            request.setCacheControl(objectMetadata.getCacheControl());
            request.setObjectStorageClass(objectMetadata.getObjectStorageClass());
            Map<String, Object> userMetadata = objectMetadata.getAllMetadata();
            if (userMetadata != null && !userMetadata.isEmpty()) {
                for (Map.Entry<String, Object> entry : userMetadata.entrySet()) {
                    request.addUserMetadata(entry.getKey(), entry.getValue().toString());
                }
            }
            // SDK设置的用户头域userHeader是透传的，不做处理可让后续使用更灵活，所以如果userHeader需要在服务端以自定义元数据形式显示
            // 在设置的用户头域userHeader前，请加上x-obs-meta-，便于服务端识别。
            request.setUserHeaders(userHeaders);
            ObjectMetadata metadata = obsClient.setObjectMetadata(request);
            logger.info("Etag: " + metadata.getEtag());
        } catch (Exception e) {
            logger.error("setObjectMetadata error", e);
        }
    }

    /**
     * 删除对象自定义元数据
     *
     * @param bucketName  桶名称
     * @param key         对象名称
     * @param userHeaders 用户自定义头域
     */
    public static void deleteObjectUserMetadata(String bucketName, String key, HashMap<String, String> userHeaders) {
        getObsClient();
        try {
            SetObjectMetadataRequest request = new SetObjectMetadataRequest();
            request.setBucketName(bucketName);
            request.setObjectKey(key);
            request.setRemoveUnset(true);
            request.setUserHeaders(userHeaders);
            obsClient.setObjectMetadata(request);
        } catch (Exception e) {
            logger.error("deleteObjectMetadata error", e);
        }
    }

    /**
     * 获取对象元数据
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @return ObjectMetadata
     */
    public static ObjectMetadata getObjectMetadata(String bucketName, String key) {
        getObsClient();
        try {
            ObjectMetadata metadata = obsClient.getObjectMetadata(bucketName, key);
            logger.info("Etag: " + metadata.getEtag());
            return metadata;
        } catch (Exception e) {
            logger.error("getObjectMetadata error", e);
        }
        return null;
    }

    /**
     * 设置对象访问权限
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param versionId  对象版本，如果不输入版本，则默认操作最新版本
     * @param acl        访问权限
     */
    public static void setObjectAcl(String bucketName, String key, String versionId, AccessControlList acl) {
        getObsClient();
        try {
            if (StringUtils.isNotBlank(versionId)) {
                obsClient.setObjectAcl(bucketName, key, acl, versionId);
            } else {
                obsClient.setObjectAcl(bucketName, key, acl);
            }
        } catch (Exception e) {
            logger.error("setObjectAcl error", e);
        }
    }

    /**
     * 获取对象访问权限
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @return AccessControlList
     */
    public static AccessControlList getObjectAcl(String bucketName, String key) {
        getObsClient();
        try {
            return obsClient.getObjectAcl(bucketName, key);
        } catch (Exception e) {
            logger.error("getObjectAcl error", e);
        }
        return null;
    }

    /**
     * 分页列举全部对象
     *
     * @param bucketName 桶名称
     * @param prefix     对象名称前缀
     * @return List<ObsObject>
     */
    public static List<ObsObject> listAllObjectsByPage(String bucketName, String prefix) {
        getObsClient();
        List<ObsObject> objects = new ArrayList<>();
        try {
            // 分页列举全部对象
            ListObjectsRequest request = new ListObjectsRequest(bucketName);
            // 设置每页100个对象
            request.setMaxKeys(100);
            request.setPrefix(prefix);
            ObjectListing result;
            do {
                result = obsClient.listObjects(request);
                objects.addAll(result.getObjects());
                request.setMarker(result.getNextMarker());
            } while (result.isTruncated());
        } catch (Exception e) {
            logger.error("listAllObjectsByPage error", e);
        }
        return objects;
    }

    /**
     * 删除对象
     *
     * @param bucketName 桶名称
     * @param key        对象名称
     * @param versionId  对象版本，如果不输入版本，则默认删除最新版本
     */
    public static void deleteObject(String bucketName, String key, String versionId) {
        getObsClient();
        try {
            if (StringUtils.isNotBlank(versionId)) {
                obsClient.deleteObject(bucketName, key, versionId);
            } else {
                obsClient.deleteObject(bucketName, key);
            }
        } catch (Exception e) {
            logger.error("deleteObject error", e);
        }
    }

    /**
     * 批量删除对象
     *
     * @param bucketName 桶名称
     * @param keys       对象名称集合
     */
    public static void deleteObjects(String bucketName, List<String> keys) {
        getObsClient();
        try {
            DeleteObjectsRequest request = new DeleteObjectsRequest(bucketName);
            for (String key : keys) {
                request.addKeyAndVersion(key);
            }
            obsClient.deleteObjects(request);
        } catch (Exception e) {
            logger.error("deleteObjects error", e);
        }
    }

    /**
     * 批量删除对象和版本
     *
     * @param bucketName 桶名称
     * @param keys       对象名称集合，objectKey为对象名称，versionId为对象版本
     */
    public static void deleteObjectsByVersions(String bucketName, List<Map<String, String>> keys) {
        getObsClient();
        try {
            // 批量删除多版本对象
            DeleteObjectsRequest request = new DeleteObjectsRequest(bucketName);
            for (Map<String, String> key : keys) {
                request.addKeyAndVersion(key.get("objectKey"), key.get("versionId"));
            }
            DeleteObjectsResult result = obsClient.deleteObjects(request);
            logger.info("deleteObjectsByVersions result: " + result.getDeletedObjectResults());
        } catch (Exception e) {
            logger.error("deleteObjectsByVersions error", e);
        }
    }

    /**
     * 删除桶内所有对象和版本
     *
     * @param bucketName 桶名称
     */
    public static void deleteAllObjectsAndVersion(String bucketName) {
        getObsClient();
        try {
            // 批量删除对象
            ListVersionsRequest request = new ListVersionsRequest(bucketName);
            // 每次批量删除100个对象
            request.setMaxKeys(100);
            ListVersionsResult result;
            do {
                result = obsClient.listVersions(request);
                DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucketName);
                // deleteRequest.setQuiet(true); // 注意此demo默认是详细模式，如果要使用简单模式，请添加本行代码
                for (VersionOrDeleteMarker v : result.getVersions()) {
                    // 获取对象和版本号
                    deleteRequest.addKeyAndVersion(v.getKey(), v.getVersionId());
                }
                if (deleteRequest.getKeyAndVersions().length > 0) {
                    DeleteObjectsResult deleteResult = obsClient.deleteObjects(deleteRequest);
                    // 获取删除成功的对象
                    logger.info("DeletedObjectResults:" + deleteResult.getDeletedObjectResults());
                    // 获取删除失败的对象
                    logger.info("ErrorResults:" + deleteResult.getErrorResults());
                } else {
                    logger.info("No object to delete");
                }
                request.setKeyMarker(result.getNextKeyMarker());
                // 如果没有开启多版本对象，就不需要设置VersionIdMarker
                request.setVersionIdMarker(result.getNextVersionIdMarker());
            } while (result.isTruncated());
        } catch (Exception e) {
            logger.error("deleteObjects error", e);
        }
    }

    /**
     * 复制对象
     *
     * @param sourceBucketName 源桶名称
     * @param sourceKey        源对象名称
     * @param versionId        源对象版本号，如果为空，则复制最新版本
     * @param targetBucketName 目标桶名称
     * @param targetKey        目标对象名称
     */
    public static void copyObject(String sourceBucketName, String sourceKey, String versionId, String targetBucketName, String targetKey) {
        getObsClient();
        try {
            // 复制多版本对象
            CopyObjectRequest request = new CopyObjectRequest();
            request.setSourceBucketName(sourceBucketName);
            request.setSourceObjectKey(sourceKey);
            // 设置要复制对象的版本号
            if (StringUtils.isNotBlank(versionId)) {
                request.setVersionId(versionId);
            }
            request.setDestinationBucketName(targetBucketName);
            request.setDestinationObjectKey(targetKey);
            // 复制时重写对象访问权限为私有读写
            // request.setAcl(AccessControlList.REST_CANNED_PRIVATE);
            CopyObjectResult result = obsClient.copyObject(request);
            logger.info("ETag: " + result.getEtag());
        } catch (Exception e) {
            logger.error("copyObject error", e);
        }
    }

    /**
     * 分段复制对象
     *
     * @param sourceBucketName 源桶名称
     * @param sourceKey        源对象名称
     * @param targetBucketName 目标桶名称
     * @param targetKey        目标对象名称
     */
    public static void copyPart(String sourceBucketName, String sourceKey, String targetBucketName, String targetKey) {
        getObsClient();
        try {
            // 初始化线程池
            ExecutorService executorService = Executors.newFixedThreadPool(20);
            // 初始化分段上传任务
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(targetBucketName, targetKey);
            InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
            String uploadId = result.getUploadId();
            logger.info("uploadId:" + uploadId);
            // 获取大对象信息
            ObjectMetadata metadata = obsClient.getObjectMetadata(sourceBucketName, sourceKey);
            // 每段复制100MB
            long partSize = 100 * 1024 * 1024L;
            long objectSize = metadata.getContentLength();
            // 计算需要复制的段数
            long partCount = objectSize % partSize == 0 ? objectSize / partSize : objectSize / partSize + 1;
            List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<>());
            // 执行并发复制段
            for (int i = 0; i < partCount; i++) {
                // 复制段起始位置
                long rangeStart = i * partSize;
                // 复制段结束位置
                long rangeEnd = (i + 1 == partCount) ? objectSize - 1 : rangeStart + partSize - 1;
                // 分段号
                int partNumber = i + 1;
                executorService.execute(
                        () -> {
                            CopyPartRequest request1 = new CopyPartRequest();
                            request1.setUploadId(uploadId);
                            request1.setSourceBucketName(sourceBucketName);
                            request1.setSourceObjectKey(sourceKey);
                            request1.setDestinationBucketName(targetBucketName);
                            request1.setDestinationObjectKey(targetKey);
                            request1.setByteRangeStart(rangeStart);
                            request1.setByteRangeEnd(rangeEnd);
                            request1.setPartNumber(partNumber);
                            CopyPartResult result1;
                            try {
                                result1 = obsClient.copyPart(request1);
                                logger.info("Part#" + partNumber + " done");
                                partEtags.add(new PartEtag(result1.getEtag(), result1.getPartNumber()));
                            } catch (ObsException e) {
                                logger.error("copyPart error", e);
                            }
                        });
            }
            // 等待复制完成
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    logger.error("copyPart error", e);
                }
            }
            // 合并段
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(targetBucketName, targetKey, uploadId, partEtags);
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
            logger.info("copyObject successfully");
        } catch (Exception e) {
            logger.error("copyObject error", e);
        }
    }

    /**
     * 判断对象是否存在
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @return true：存在，false：不存在
     */
    public static boolean doesObjectExist(String bucketName, String objectKey) {
        getObsClient();
        try {
            return obsClient.doesObjectExist(bucketName, objectKey);
        } catch (Exception e) {
            logger.error("doesObjectExist error", e);
        }
        return false;
    }

    /**
     * 获取对象临时访问地址
     *
     * @param bucketName 桶名称
     * @param objectKey  对象名称
     * @param expires    过期时间，单位：秒
     * @return 临时访问地址
     */
    public static String getObjectTemporarySignatureUrl(String bucketName, String objectKey, int expires) {
        getObsClient();
        try {
            TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires);
            request.setBucketName(bucketName);
            request.setObjectKey(objectKey);
            TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
            return response.getSignedUrl();
        } catch (Exception e) {
            logger.error("getObjectTemporarySignatureUrl error", e);
        }
        return null;
    }

    /**
     * 设置桶多版本状态
     *
     * @param bucketName 桶名称
     * @param status     桶多版本状态
     */
    public static void setBucketVersioning(String bucketName, VersioningStatusEnum status) {
        getObsClient();
        try {
            // 桶多版本状态
            obsClient.setBucketVersioning(
                    bucketName, new BucketVersioningConfiguration(status));
        } catch (Exception e) {
            logger.error("setBucketVersioning error", e);
        }
    }

    /**
     * 查看桶多版本状态
     *
     * @param bucketName 桶名称
     * @return 桶多版本状态
     */
    public static VersioningStatusEnum getBucketVersioning(String bucketName) {
        getObsClient();
        try {
            // 查看桶多版本状态
            BucketVersioningConfiguration status = obsClient.getBucketVersioning(bucketName);
            return status.getVersioningStatus();
        } catch (Exception e) {
            logger.error("getBucketVersioning error", e);
        }
        return null;
    }

    public static List<VersionOrDeleteMarker> listObjectAllVersions(String bucketName, String prefix) {
        getObsClient();
        List<VersionOrDeleteMarker> list = new ArrayList<>();
        try {
            // 分页列举全部对象
            ListVersionsResult result;
            ListVersionsRequest request = new ListVersionsRequest(bucketName, 100);
            request.setPrefix(prefix);
            do {
                result = obsClient.listVersions(request);
                list.addAll(Arrays.asList(result.getVersions()));
                request.setKeyMarker(result.getNextKeyMarker());
                request.setVersionIdMarker(result.getNextVersionIdMarker());
            } while (result.isTruncated());
        } catch (Exception e) {
            logger.error("listObjectAllVersions error", e);
        }
        return list;
    }
}
